package com.atguigu.gmall.product.service.impl;

import com.atguigu.gmall.common.cache.GmallCache;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.product.model.SkuImage;
import com.atguigu.gmall.product.model.SkuInfo;
import com.atguigu.gmall.product.service.ProductApiService;
import com.atguigu.gmall.product.service.SkuImageService;
import com.atguigu.gmall.product.service.SkuInfoService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author XIN
 * 2023/4/24
 */
@Service
public class ProductApiServiceImpl implements ProductApiService {
	@Autowired
	private SkuInfoService skuInfoService;
	@Autowired
	private SkuImageService skuImageService;
	@Autowired
	private RedisTemplate redisTemplate;
	@Autowired
	private RedissonClient redissonClient;
	/**
	 * 根据SkuID查询SKU商品信息包含图片列表
	 */
	@Override
	public SkuInfo getSkuInfo(Long skuId) {
		try {
			String dataKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKUKEY_SUFFIX;
			SkuInfo skuInfo = (SkuInfo) redisTemplate.opsForValue().get(dataKey);
			if (skuInfo != null) {
				return skuInfo;
			}
			String lockKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKULOCK_SUFFIX;
			String lockVal = UUID.randomUUID().toString().replaceAll("-", "");
			Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(lockKey, lockVal, RedisConst.SKULOCK_EXPIRE_PX1, TimeUnit.SECONDS);
			if (aBoolean) {
				try {
					skuInfo = this.getSkuInfoFromDB(skuId);
					if (skuInfo==null){
						redisTemplate.opsForValue().set(dataKey, skuInfo,RedisConst.SKUKEY_TEMPORARY_TIMEOUT,TimeUnit.SECONDS);
						return skuInfo;
					}else {
						redisTemplate.opsForValue().set(dataKey, skuInfo,RedisConst.SKUKEY_TIMEOUT,TimeUnit.SECONDS);
						return skuInfo;
					}
				}catch (Exception e){
					throw new RuntimeException(e);
				}finally {
					String luaText = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
							"then\n" +
							"    return redis.call(\"del\",KEYS[1])\n" +
							"else\n" +
							"    return 0\n" +
							"end";
					DefaultRedisScript<Long> script = new DefaultRedisScript<>();
					script.setScriptText(luaText);
					script.setResultType(Long.class);
					redisTemplate.execute(script, Collections.singletonList(lockKey),new String[]{lockVal});
				}
			}else {
				TimeUnit.MILLISECONDS.sleep(500);
				return this.getSkuInfo(skuId);
			}
		}catch (Exception e){
			return this.getSkuInfoFromDB(skuId);
		}
	}
	
	@Override
	@GmallCache(prefix="sku",suffix="info")
	public SkuInfo getSkuInfoFromDB(Long skuId) {
		SkuInfo skuInfo = skuInfoService.getById(skuId);
		if(skuInfo!=null){
			LambdaQueryWrapper<SkuImage> queryWrapper = new LambdaQueryWrapper<>();
			queryWrapper.eq(SkuImage::getSkuId, skuId);
			List<SkuImage> list = skuImageService.list(queryWrapper);
			skuInfo.setSkuImageList(list);
			return skuInfo;
		}
		return null;
	}
	
	@Override
	public SkuInfo getSkuInfoByRedisson(Long skuId) {
		try {
			String dataKey = RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKUKEY_SUFFIX;
			SkuInfo skuInfo= (SkuInfo) redisTemplate.opsForValue().get(dataKey);
			if (skuInfo!= null){
				return skuInfo;
			}
			String lockKey = RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKULOCK_SUFFIX;
			String lockVal = UUID.randomUUID().toString().replaceAll("-", "");
			RLock lock =redissonClient.getLock(lockKey);
			boolean b = lock.tryLock(RedisConst.SKULOCK_EXPIRE_PX2, RedisConst.SKULOCK_EXPIRE_PX1, TimeUnit.SECONDS);
			if (b) {
				try {
					skuInfo = this.getSkuInfoFromDB(skuId);
					if (skuInfo==null){
						redisTemplate.opsForValue().set(dataKey,skuInfo,RedisConst.SKUKEY_TEMPORARY_TIMEOUT,TimeUnit.SECONDS);
						return skuInfo;
					}else {
						redisTemplate.opsForValue().set(dataKey,skuInfo,RedisConst.SKUKEY_TIMEOUT,TimeUnit.SECONDS);
						return skuInfo;
					}
				}finally {
					lock.unlock();
				}
			}else {
				TimeUnit.MILLISECONDS.sleep(500);
				return this.getSkuInfoByRedisson(skuId);
			}
		}catch (Exception e)
		{
			return this.getSkuInfoFromDB(skuId);
		}
	}
}
